Empfaenger : /FIDO/POWER_BAS Absender : Brian Mclaughlin @ 1:105/314.0 (COM-DAT III, Hillsboro OR) Betreff : MORE ASM INFO ---------------------------------------------------------------------- ======= TIPS ON CONVERTING QB EXTERNAL ASM TO PB INLINE ASM ======== This discussion assumes you are attempting to convert external ASM code written for QB4x, to make it compatible with the PB3x inline assembler. It doesn't address how to convert ASM code written to be assembled as stand-alone .EXE or .COM files. It also assumes at least a slight, nodding aquaintance with assembly language. It doesn't address the conversion of string routines. The most important part of the ASM code to look at first is the code that picks the parameters off the stack and puts them into registers. As far as I know, this code must be changed in most cases. Generally the first step is to know how the original QB code passed its parameters -- yes, you gotta know the DECLARE statement! Unless the QB DECLARE includes the keywords SEG or BYVAL before a parameter, the parameter is passed by reference. Every QB parameter passed by reference is pushed onto the stack as an offset address relative to DS. BUT every PB parameter passed by reference is pushed onto the stack as a four-byte seg:offs address, which should be loaded using Les or Lds. Let me demonstrate. A lot of old MASM external routines for QB look something like this: FooBar PROC FAR ; one parameter, Foo% on the stack Push BP ; save old BP for restoration later Mov BP, SP ; nail down the stack pointer before it moves Push SI ; we'll trash SI and DI later on, so save them Push DI Mov BX, [BP+6] ; DS:BX now points to Foo% Mov AX, [BX] ; Ax contains the value of Foo% Converting this snippet to PB's inline ASM is simple. You don't need the first two lines. Since PB's compiler knows where Foo% is located on the stack already you won't need to tell PB the stack-relative address. In fact, you shouldn't need to mess with BP at all. You would load AX with a default BYREF parameter this way: SUB FooBar (Foo%) 'this is passed BYREF !Push SI ; we'll trash SI and DI later on, so save them !Push DI !Les BX, Foo% ;ES:BX now points to Foo% !Mov AX, ES:[BX] ;AX contains the value of Foo% However, assembly language code written for QB may not address the stack in the way my first example showed. Instead it may use the "simplified" directives introuced with MASM 5.1. In that case, the first line of the procedure might look more like this: FooBar PROC FAR USES SI DI, Foo:WORD Mov BX, Foo Mov AX, [BX] The simplified directive USES directs MASM to insert those two pushes (of SI, DI) in the assembled routine -- along with the paired pops at the end of the code, too. The other directive: Foo:WORD tells MASM that a word-sized parameter is on the stack and the code will reference it by the name of Foo. All in all, this "simplified" code would translate to inline ASM just the same: SUB FooBar (Foo%) !Push SI ; we'll trash SI and DI later on, so save them !Push DI !Les BX, Foo% ;ES:BX points to value of Foo% !Mov AX, ES:[BX] ;AX contains the value of Foo% One big conversion problem is that PB provides some of it's extra features by requiring far addressing where QB uses near addressing, and sometimes there just won't be enough registers to go around when you move QB asm code to PB. This can't be solved by cookbook methods. However, the not-enough-registers problem has always been at the heart of programming the 80*86 processors, so it is nothing new! Here is the good news: if the old QB code you are converting passed its parameters either BYVAL or SEG, the chances are you'll be OK, since PB treats both BYVAL and SEG parameters pretty much identically to QB. Also, PB3 considers ES to be a scratch register (one you can trash with a clear conscience) so when QB code pushes ES at the beginning, so it can be restored at the end, those two steps can be dropped. One good tip is to convert as many BYREF (the default) parameters to BYVAL as you can. This is a tad faster than BYREF, but more importantly, it SAVES REGISTERS, since you can do this: SUB FooBar (BYVAL Foo%) !Mov AX, Foo% ;AX contains the value of Foo% This means you don't trash ES, BX, SI or DI to load the value of Foo% into AX. So, when can you use BYVAL?? Easy. Any time the routine won't need to FIND or CHANGE a parameter you can pass it BYVAL. Or, to put it another way, if the routine *only* needs the parameter's value and it isn't using the parameter to pass back information, pass the parameter BYVAL. ======================= SOME GENERAL TIPS ============================ Once you solve the not-enough registers problem there are still some useful tips to avoid the "gotchas". I don't know them all yet. Some of the ones I know about are: I've seen people posting messages saying that whenever you insert ASM statements in PB code that PB's compiler will take steps "silently" to push various registers for you and pop them again afterwards. This information is FALSE!!! If your inline code changes SI, DI, DS, CS, SS, SP, or BP, you MUST save them yourself and restore them, too. Failure to do so may not crash your machine, but you are absolutely risking that possibility. IMPORTANT: In PB3.0x, DON'T return values from an inline FUNCTION using the same methods as an external routine -- e.g. returning an integer value by putting it in AX, or a long integer in DX:AX. (According to the README, PB31 allows this, but I haven't experimented with it, yet.) A PB30x FUNCTION written entirely with ASM statements is a FUNCTION written in BASIC, so you must end it like one: FUNCTION FunctionName& (Parameter%) STATIC SomeValue& 'this variable has a non-stack address ASM ... ASM ... FunctionName& = SomeValue& END FUNCTION The only way to actually return the value in PB30 is to put it into a variable first and then assign the return value from the variable. You could declare a LOCAL or STATIC variable inside your FUNCTION or you could just do it the old-fashioned way, change the FUNCTION to a SUB, send the variable as a parameter and fill it before exiting. But you CAN'T send the value back in a register. Another minor gotcha, don't use the comment delimiter ' on lines that begin with ASM or !. Instead use the semicolon delimiter. But your labels should NOT be given the ASM or ! specifier in front of them, so after labels you SHOULD use ' and not ; for comments. This last one is cute - MASM and TASM specify hexidecimal numbers like so: 0FF34h. PB's inline ASM statements are considered to be BASIC statements by the compiler, so it insists you use the BASIC format: &HFF34. This will seem like heresy to ASM programmers! AH yes! You should be aware that when you pass a fixed-length string to a routine in QB, by default QB will sneakily change the fixed-length string into a variable length string, by copying it; then QB passes the descriptor of the variable length string. The only way to scotch this behavior is to declare the parameter as passed by SEG. Look for this in your QB code. PB3, on the other hand, passes fixed length strings as a seg:off address of the string data in memory. It makes no conversion to variable length. There must be some others I'm forgetting. But one of these tips may help someone out of their perplexity. =================== INLINE STRING FUNCTIONS ==================== The PB3 manuals don't make it especially clear how to write a string FUNCTION using inline ASM statements to create/manupulate a string and then return that string as the FUNCTION's return value. The first thing which you MUST know is that anytime you use GETSTRLOC or GETSTRALLOC in your code, you must DECLARE them like any other FUNCTIONs in your code. The proper form for these declarations can be found in the online help, under the topic: Internal Procedures. You can call any PowerBASIC internal procedure from inline ASM. 1) push any required parameters -- use: ASM Push XX 2) call the procedure -- use: ASM Call INTERNAL_PROCEDURE 3) manipulate the values returned. To RETURN a string from a FUNCTION, you can use this method: FUNCTION AnyString$ ' creates a string using GETSTRALLOC (we'll pretend) ' locates the string using GETSTRLOC (presumably) ' fills the string with data, ends with the string's handle in AX. LOCAL Return$ . 'the string is created and filled here . 'and AX holds the string handle now . ASM Mov Return$, AX . . ' clean up the stack here, if needed . AnyString$ = Return$ END FUNCTION This snippet declares Return$ as LOCAL, but it could omit that step and take advantage of the fact that PowerBASIC will allocate (previously undeclared) variables for you when they are first encountered. That's not especially good practise, but it works. One of the strengths of inline ASM is the fact that it can so easily be mixed with BASIC statements and BASIC variables -- because the compiler "sees" inline ASM statements as part of the BASIC language. For example, you can use the names of BASIC variables anywhere in your inline ASM statements and PB3 will compile them smoothly. The only hitch is that YOU must know what your variable really means. Depending on the circumstances, the variable name Some$ could represent a four byte address which points to a string handle or Some$ could represent the handle itself (as a two-byte value). The first is true if Some$ was a BYREF parameter passed to a procedure. The second would be true if Some$ were a BYVAL parameter or local string variable. This can be quite a leap for a BASIC programmer who is used to thinking of Some$ as the string data itself!